/*
 * bootloader.c
 *
 *  Created on: Dec 25, 2023
 *      Author: jetpo
 */
#include "bootloader.h"
// #include "uart_print.h"

#include "ymodem.h"
#include "stdio.h"

#define STOP_UPLOAD -1
#define WAIT_UPLOADING 1
#define SEND_FILE_PACKET 2
#define SEND_EOT 3
#define SEND_END_PACKET 4
#define SEND_SUCCESS 5

/**
 * @brief 跳转到APP
 *
 */
void goto_application()
{
}

int begin_app_update(int timeout, UART_HandleTypeDef *puart_update)
{
    printf("\r\n");
    printf("\r\n");
    printf("-------------------------------------------------------------------------\r\n");
    printf("\r\n");
    printf("                          BEGIN UPDATE APPLICATION                       \r\n");
    printf("\r\n");
    printf("\r\n");
    printf("                           END UPDATE APPLICATION                        \r\n");

    // 解锁内存
    // -------
    HAL_StatusTypeDef update_status;
    update_status = HAL_FLASH_Unlock();
    if (HAL_OK != update_status)
    {
        printf(">> flash unlock FAIL!!!\r\n");
        printf(">> application update abortted...\r\n");
        return -1;
    }
    else
    {
        printf(">> flash unlock SUCCESS!!!\r\n");
        printf(">> ready for uploading...\r\n");
    }

    // ymodem传输初始化
    // ---------------
    printf(">> waitting for ymodem transmit...\r\n");

    uint8_t data[PACKET_DATA_1K_SIZE + PACKET_OVERHEAD_SIZE];
    uint32_t size, file_size;
    char file_name[FILE_NAME_LENGTH];
    uint16_t file_name_len;

    // ymodem传输流程
    // -------------
    uint16_t error_count = 0;
    YmodemReceiveType ymodem_recv_type;
    YmodemReceiveSession ymodem_session = YMODEM_WaittingConnection;

    while (YMODEM_ReceiveSuccess < ymodem_session)
    {
        ymodem_recv_type = ymodem_comm(&ymodem_session, puart_update, 500, data, &size, file_name, &file_name_len, &file_size);
        if (YMODEM_WaittingConnection == ymodem_session || YMODEM_ErrorPacket == ymodem_recv_type )
        {
            error_count += 1;
        }
        else if (YMODEM_InfoReceived == ymodem_session)
        {
            // 通信建立且收信息包正常
            // 擦除原有内存
            // TODO: 内存中APP分2个区, A区运行, B区存储更新数据
            //       B区更新完成后置标志位为已下载新程序, 下一次上电将B区程序搬运到A区, 再置B区标志位为已更新

            // FLASH擦除
            // --------
            // TODO: 擦除失败退出ymodem
            FLASH_EraseInitTypeDef eraseInitStruct;
            uint32_t sectorError;

            eraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
            eraseInitStruct.PageAddress = APP_FLASH_ADDR; // 程序起点
            eraseInitStruct.NbPages = APP_FLASH_PAGES;                 // 擦除页数, 1页对应2K
            update_status = HAL_FLASHEx_Erase(&eraseInitStruct, &sectorError);
        }
        else if (YMODEM_FileReceiving == ymodem_session && YMODEM_FilePacket == ymodem_recv_type) 
        {
            static uint32_t application_start_address = APP_FLASH_ADDR; // 程序的起点
            static uint32_t application_write_idx = 0;              // 写入起点的偏移量

            for (uint32_t i = 0; i < size / 2; i++) // CAUTION: 发送的文件必须2字节对齐
            {
                uint32_t idx = PACKET_DATA_INDEX + i * 2;
                uint16_t halfword_data = data[idx] | (data[idx + 1] << 8); // 每次写2个B
                update_status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
                                                  application_start_address + application_write_idx,
                                                  halfword_data);
                if (HAL_OK == update_status)
                {
                    application_write_idx += 2;
                }
                else
                {
                    // TODO: 内存错误, 退出更新
                    break;
                }

                if (application_write_idx >= file_size) // 下一个写入的位置大于等于文件长度, 则表明上传完成
                {
                    break;
                }
            }
        }
        if (error_count > 5)
            ymodem_session = YMODEM_StopWithError;
    }
  
    // TODO: 发送ymodem取消标志, 以使得通用上位机可以正常显示串口信息
    HAL_FLASH_Lock(); // 重新锁住FLASH
    printf("\r\n");
    printf(">> flash relocked\r\n");

    // 更新结果打印
    // -----------
    printf("\r\n");
    printf("\r\n");
    if (ymodem_session >= YMODEM_EOTReceivedOnce || ymodem_session == YMODEM_ReceiveSuccess)
    {
        printf(">> upload file name:%s\r\n", file_name);
        printf(">> upload file size:%dB\r\n", (int)file_size);
        printf(">> application uploaded SUCCESS!!!\r\n");
    }
    else
    {
        printf(">> application uploaded FAIL!!!\r\n");
    }

    printf("\r\n");
    printf("\r\n");
    printf("                           END UPDATE APPLICATION                        \r\n");
    printf("\r\n");
    printf("-------------------------------------------------------------------------\r\n");

    // 程序跳转
    // -------
    if (ymodem_session >= YMODEM_EOTReceivedOnce || ymodem_session == YMODEM_ReceiveSuccess)
    {
        goto_application();
    }
    return 0;
}

const char DATE[12] = __DATE__;
const char TIME[9] = __TIME__;
void check_update(int check_interval, int timeout, UART_HandleTypeDef *puart_update)
{
    HAL_Delay(100); // 延迟100ms开始检测
    printf("\r\n");
    printf("\r\n");
    printf("\r\n");
    printf("-------------------------------------------------------------------------\r\n");
    printf("\r\n");
    printf("                             BEGIN UPDATE CHECKING                       \r\n");
    printf("\r\n");
    printf(">> Bootloader ver%d.%02d\r\n", BOOTLOADER_VER_MAJOR, BOOTLOADER_VER_MINOR);
    printf(">> build at %s, %s\r\n", DATE, TIME);

    // 升级检测标志采样
    uint8_t checked_count = 0;
    for (uint8_t i = 0; i < 3; i++)
    {
        if (1 == HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12))
        {
            checked_count += 1;
            printf(">> updating flag detected x%d\r\n", checked_count);
        }
        else
        {
            printf(">> updating flag detected negative x%d\r\n", i - checked_count + 1);
        }
        HAL_Delay(check_interval);
    }

    if (2 <= checked_count)
    {
        printf(">> updating flag detecting PASSED\r\n");
    }
    else
    {
        printf(">> updating flag detecting FAILED\r\n");
    }

    printf("\r\n");
    printf("\r\n");
    printf("                           END UPDATE CHECKING\r\n");
    printf("-------------------------------------------------------------------------\r\n");
    printf("\r\n");
    printf("\r\n");

    if (2 <= checked_count)
        begin_app_update(timeout, puart_update);
}
